package cn.ava.ymcc.service.impl;

import cn.ava.exception.GlobleException;
import cn.ava.ymcc.constants.*;
import cn.ava.ymcc.domain.Course;
import cn.ava.ymcc.domain.CourseOrder;
import cn.ava.ymcc.domain.CourseOrderItem;
import cn.ava.ymcc.dto.CourseOrderDto;
import cn.ava.ymcc.dto.Order2PayDto;
import cn.ava.ymcc.dto.PayNotifyDto;
import cn.ava.ymcc.dto.PreKillOrder;
import cn.ava.ymcc.feign.CourseFeignClient;
import cn.ava.ymcc.mapper.CourseOrderMapper;
import cn.ava.ymcc.result.JSONResult;
import cn.ava.ymcc.service.ICourseOrderItemService;
import cn.ava.ymcc.service.ICourseOrderService;
import cn.ava.ymcc.utils.AssertUtil;
import cn.ava.ymcc.utils.CodeGenerateUtils;
import cn.ava.ymcc.vo.Course2OrderVo;
import cn.ava.ymcc.vo.Courseinfo2OrderinfoVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ava
 * @since 2022-08-18
 */
@Service
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    private static final Long LOGINID = 2353L;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 下单业务逻辑.生成订单号
     * @param dto
     */
    @Override
    @Transactional
    public String placeOrder(CourseOrderDto dto) {
        // jsr303参数非空校验
        // 使用工具类截取到id
        String courseId = StringUtils.join(dto.getCourseIds(), ",");
        // 防重token校验,生成redis的key
        String tokenKey = String.format(CommonConstants.TokenConstant.TOKEN_KEY, LOGINID, courseId);
        // 从redis中获取到防重token
        Object token = redisTemplate.opsForValue().get(tokenKey);
        // 校验是否存在,如果不存在说明已经下单过了或者订单不存在
        AssertUtil.isNotNull(token,"订单重复或不存在,请重试!");
        // 存在,判断防重token是否一致
        AssertUtil.isTrue(dto.getToken().equals(token.toString()),"订单不存在!");
        // 获取到课程数据
        JSONResult jsonResult = courseFeignClient.info(courseId);
        // 判断服务是否可用
        AssertUtil.isTrue(jsonResult.isSuccess(), jsonResult.getMessage());
        // 判断课程是否存在
        Object data = jsonResult.getData();
        Courseinfo2OrderinfoVo courseOrderVo = JSONObject.parseObject(JSON.toJSONString(data), Courseinfo2OrderinfoVo.class);
        AssertUtil.isNotNull(courseOrderVo,"课程信息异常!");
        // 获取到课程信息
        List<Course2OrderVo> courseInfos = courseOrderVo.getCourseInfos();
        // 创建主订单
        CourseOrder order = createCourseOrder(dto.getPayType(), courseOrderVo.getTotalAmount(), courseInfos);
        // 设置扩展参数包含loginId,courseId
        Map<String, Object> extParam = new HashMap<>();
        extParam.put("courseId",courseId);
        extParam.put("loginId",LOGINID);
        // 创建支付单消息
        Order2PayDto payDto = new Order2PayDto(
                order.getTotalAmount(),
                dto.getPayType(),
                order.getOrderNo(),
                LOGINID,
                JSONObject.toJSONString(extParam),
                order.getTitle()
        );
        Message<String> pay = MessageBuilder.withPayload(JSONObject.toJSONString(payDto)).build();
        // 发送mq事务消息
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                "tx-order2Pay-group",
                "order-topic:pay-tags",
                pay,
                order
        );
        SendStatus sendStatus = sendResult.getSendStatus();
        LocalTransactionState localTransactionState = sendResult.getLocalTransactionState();
        // 校验事务消息是否成功
        if(sendStatus != SendStatus.SEND_OK || localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
            throw new GlobleException("下单失败,请稍后重试!","404");
        }
        // 删除token,做防重校验
        redisTemplate.delete(tokenKey);
        // 返回订单号
        log.info("订单创建成功!{} ",sendStatus);
        try {
            Map<String, String> map = new HashMap<>();
            map.put("orderNo",order.getOrderNo());
            // 发送延迟消息,校验订单是否支付超时
            Message<String> delayMsg = MessageBuilder.withPayload(JSONObject.toJSONString(map)).build();
            SendResult syncSend = rocketMQTemplate.syncSend(
                    "delay-topic:cancel-tags",
                    delayMsg,
                    3000,
                    5
            );
            if (syncSend.getSendStatus() != SendStatus.SEND_OK) {
                log.info("发送订单超时延时消息失败!");
                //
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("发送订单超时延时消息异常!");
        }
        log.info("发送订单超时延时消息");
        return order.getOrderNo();
    }

    /**
     * 创建主订单对象与子订单明细对象
     * @param payType
     * @param totalAmount
     * @param courseInfos
     * @return
     */
    CourseOrder createCourseOrder(Integer payType, BigDecimal totalAmount, List<Course2OrderVo> courseInfos) {
        Date now = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(now);
        order.setOrderNo(CodeGenerateUtils.generateOrderSn(LOGINID));
        order.setTotalAmount(totalAmount);
        order.setStatusOrder(CommonConstants.OrderPayState.STATE_WAIT);
        order.setUserId(LOGINID);
        order.setPayType(payType);
        order.setTotalCount(NumberUtils.INTEGER_ZERO);
        // 遍历课程集合,添加创建订单明细信息
        StringBuilder sb = new StringBuilder();
        sb.append("购买课程【");
        courseInfos.forEach(c ->{
            // 业务校验课程状态是否为上线
            Course course = c.getCourse();
            Integer status = course.getStatus();
            AssertUtil.isTrue(CommonConstants.CourseStateConstant.STATE_ONLINE.equals(status),"课程状态异常,请重试!");
            // 创建订单明细数据
            CourseOrderItem item = new CourseOrderItem();
            item.setAmount(c.getCourseMarket().getPrice());
            item.setCount(CourseOrder.ORDER_COUNT);
            item.setCreateTime(now);
            item.setCourseId(course.getId());
            item.setCourseName(course.getName());
            item.setCoursePic(course.getPic());
            item.setOrderNo(order.getOrderNo());
            order.setTotalCount(order.getTotalCount() + CourseOrder.ORDER_COUNT);
            order.getItems().add(item); // 添加订单明细数据
            sb.append(course.getName()).append(",");
        });
        sb.append("】总金额: ").append(order.getTotalAmount());
        order.setTitle(sb.toString());
        return order;
    }

    /**
     * 保存主订单与订单明细
     * @param order
     */
    @Override
    public void saveAndItem(CourseOrder order) {
        // 校验是否已经存在
        CourseOrder courseOrder = checkOrder(order.getOrderNo());
        AssertUtil.isNull(courseOrder,"订单已存在,请忽重复下单!");
        // 保存订单
        insert(order);
        // 保存子订单
        order.getItems().forEach(item -> {
            item.setOrderId(order.getId());
            courseOrderItemService.insert(item);
        });
    }

    /**
     * 校验订单是否存在
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrder checkOrder(String orderNo) {
        Wrapper<CourseOrder> query = new EntityWrapper<>();
        query.eq("order_no",orderNo);
        return selectOne(query);
    }


    /**
     * 处理异步支付通知后修改订单状态
     * @param notifyDto
     */
    @Override
    @Transactional
    public void handlerPayOrderNotify(PayNotifyDto notifyDto) {
        CourseOrder courseOrder = checkOrder(notifyDto.getOrderNo());
        // 业务校验幂等性
        if (CommonConstants.OrderPayState.STATE_WAIT.equals(courseOrder.getStatusOrder())) {
            // 只有为待支付状态才进行处理
            courseOrder.setStatusOrder(CommonConstants.OrderPayState.STATE_SUCCESS);
            courseOrder.setUpdateTime(new Date());
            updateById(courseOrder);
        }else { // 否则状态为取消或失败
            // 需要执行退款流程
            /*
            * 1.托底方案,通知运维客服处理
            * 2.修改异常状态
            * 3.记录异常记录表
            */
        }
    }

    /**
     * 订单超时处理
     * @param jsonResult
     */
    @Override
    @Transactional
    public void cancelCourseOrder(String jsonResult) {
        Map<String,String> map =  JSONObject.parseObject(jsonResult, Map.class);
        String orderNo = map.get("orderNo");
        String semaphoreKey = map.get("semaphoreKey");
        CourseOrder courseOrder = checkOrder(orderNo);
        if (courseOrder == null) {
            return; // 订单不存在直接结束
        }
        if (!CommonConstants.OrderPayState.STATE_WAIT.equals(courseOrder.getStatusOrder())) {
            // 只要不是待支付状态直接结束
            return;
        }
        // 校验是否是秒杀订单,如果是那么归还库存
        if (semaphoreKey != null) {
            // 归还库存
            RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
            semaphore.addPermits(CommonConstants.KillCourseState.KILL_COUNT_ONE);
            // 删除防重复秒杀token
            String killToken = map.get("killToken");
            redisTemplate.delete(killToken);
            log.info("订单支付超时,归还秒杀库存!");
        }
        // 取消订单
        courseOrder.setUpdateTime(new Date());
        courseOrder.setStatusOrder(CommonConstants.OrderPayState.STATE_CANCEL);
        log.info("订单支付超时,自动取消!");
        updateById(courseOrder);
    }

    /**
     * 秒杀订单创建
     * @param dto
     * @return
     */
    @Override
    public String killPlaceOrder(CourseOrderDto dto) {
        // jsr303参数非空校验
        String orderNo = dto.getOrderNo();
        AssertUtil.isNotNull(orderNo,"参数异常!");
        // 从redis获取到预存订单
        PreKillOrder preKillOrder = (PreKillOrder) redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(preKillOrder,"提交订单超时,已失效!");
        // 防重token校验,生成redis的key
        String tokenKey = String.format(CommonConstants.TokenConstant.TOKEN_KEY, LOGINID, preKillOrder.getCourseId());
        // 从redis中获取到防重token
        Object token = redisTemplate.opsForValue().get(tokenKey);
        // 校验是否存在,如果不存在说明已经下单过了或者订单不存在
        AssertUtil.isNotNull(token,"订单重复或不存在,请重试!");
        // 存在,判断防重token是否一致
        AssertUtil.isTrue(dto.getToken().equals(token.toString()),"订单不存在!");
        // 创建秒杀订单
        CourseOrder order = getCourseOrder(dto, preKillOrder);
        // 设置扩展参数包含loginId,courseId
        Map<String, Object> extParam = new HashMap<>();
        extParam.put("courseId",preKillOrder.getCourseId());
        extParam.put("loginId",LOGINID);
        // 创建支付单消息
        Order2PayDto payDto = new Order2PayDto(
                order.getTotalAmount(),
                dto.getPayType(),
                order.getOrderNo(),
                LOGINID,
                JSONObject.toJSONString(extParam),
                order.getTitle()
        );
        Message<String> pay = MessageBuilder.withPayload(JSONObject.toJSONString(payDto)).build();
        // 发送mq事务消息
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                "tx-order2Pay-group",
                "order-topic:pay-tags",
                pay,
                order
        );
        SendStatus sendStatus = sendResult.getSendStatus();
        LocalTransactionState localTransactionState = sendResult.getLocalTransactionState();
        // 校验事务消息是否成功
        if(sendStatus != SendStatus.SEND_OK || localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
            throw new GlobleException("下单失败,请稍后重试!","404");
        }
        // 删除token,做防重校验
        redisTemplate.delete(tokenKey);
        log.info("订单创建成功!{} ",sendStatus);
        // 拼接信号量key
        String semaphoreKey = preKillOrder.getActivityId() + ":" + preKillOrder.getKillCourseId();
        // 防重复秒杀token
        String killToken = LOGINID + ":" + preKillOrder.getActivityId() + ":" + preKillOrder.getKillCourseId();
        Map<String, String> map = new HashMap<>(); // 封装订单号与信号量Key
        map.put("semaphoreKey",semaphoreKey);
        map.put("orderNo",dto.getOrderNo());
        map.put("killToken",killToken);
        try {
            // 发送延迟消息,校验订单是否支付超时
            Message<String> delayMsg = MessageBuilder.withPayload(JSONObject.toJSONString(map)).build();
            SendResult syncSend = rocketMQTemplate.syncSend(
                    "delay-topic:cancel-tags",
                    delayMsg,
                    3000,
                    5 // 3分钟
            );
            if (syncSend.getSendStatus() != SendStatus.SEND_OK) {
                log.info("发送订单超时延时消息失败!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("发送订单超时延时消息异常!");
        }
        log.info("发送订单超时延时消息");
        // 删除预存订单
        redisTemplate.delete(orderNo);
        // 返回订单号
        return order.getOrderNo();
    }



    /**
     * 创建秒杀主订单
     * @param dto
     * @param preKillOrder
     * @return
     */
    private CourseOrder getCourseOrder(CourseOrderDto dto, PreKillOrder preKillOrder) {
        // 创建主订单
        Date now = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(now);
        order.setOrderNo(preKillOrder.getOrderNo());
        order.setTotalAmount(preKillOrder.getTotalAmount());
        order.setStatusOrder(CommonConstants.OrderPayState.STATE_WAIT);
        order.setUserId(LOGINID);
        order.setPayType(dto.getPayType());
        order.setTotalCount(preKillOrder.getTotalCount());
        order.setTitle("秒杀课程【" + preKillOrder.getCourseName() + "】");
        // 创建订单明细数据
        CourseOrderItem item = new CourseOrderItem();
        item.setAmount(order.getTotalAmount());
        item.setCount(CourseOrder.ORDER_COUNT);
        item.setCreateTime(now);
        item.setCourseId(preKillOrder.getCourseId());
        item.setCourseName(preKillOrder.getCourseName());
        item.setCoursePic(preKillOrder.getCoursePic());
        item.setOrderNo(preKillOrder.getOrderNo());
        order.setTotalCount(preKillOrder.getTotalCount());
        order.getItems().add(item); // 添加订单明细数据
        return order;
    }


}
